import env from './env'
import { defineVirtualModule, generageImports } from './_utils'

export default defineVirtualModule(
  'wasm-virutal:utils',
  `
  ${generageImports([env, ['isNode']])}
  /*#__PURE__*/ import { fromByteArray, byteLength, toByteArray } from "base64-js";
  
  const MAX_ARGUMENTS_LENGTH = 0x1000;
  const _alphabet = "0123456789abcdef";
  const _hexSliceLookupTable = new Array(256);
  for (let i = 0; i < 16; ++i) {
    const i16 = i * 16;
    for (let j = 0; j < 16; ++j) {
      _hexSliceLookupTable[i16 + j] = _alphabet[i] + _alphabet[j];
    }
  }
  
  const _hexSlice = (buf, start, end) => {
    const len = buf.length;
  
    if (!start || start < 0) start = 0;
    if (!end || end < 0 || end > len) end = len;
  
    let out = "";
    for (let i = start; i < end; ++i) {
      out += _hexSliceLookupTable[buf[i]];
    }
    return out;
  };
  
  const _utf8Slice = (buf, start, end) => {
    end = Math.min(buf.length, end);
    const res = [];
  
    let i = start;
    while (i < end) {
      const firstByte = buf[i];
      let codePoint = null;
      let bytesPerSequence = firstByte > 0xef ? 4 : firstByte > 0xdf ? 3 : firstByte > 0xbf ? 2 : 1;
  
      if (i + bytesPerSequence <= end) {
        let secondByte, thirdByte, fourthByte, tempCodePoint;
  
        switch (bytesPerSequence) {
          case 1:
            if (firstByte < 0x80) {
              codePoint = firstByte;
            }
            break;
          case 2:
            secondByte = buf[i + 1];
            if ((secondByte & 0xc0) === 0x80) {
              tempCodePoint = ((firstByte & 0x1f) << 0x6) | (secondByte & 0x3f);
              if (tempCodePoint > 0x7f) {
                codePoint = tempCodePoint;
              }
            }
            break;
          case 3:
            secondByte = buf[i + 1];
            thirdByte = buf[i + 2];
            if ((secondByte & 0xc0) === 0x80 && (thirdByte & 0xc0) === 0x80) {
              tempCodePoint = ((firstByte & 0xf) << 0xc) | ((secondByte & 0x3f) << 0x6) | (thirdByte & 0x3f);
              if (tempCodePoint > 0x7ff && (tempCodePoint < 0xd800 || tempCodePoint > 0xdfff)) {
                codePoint = tempCodePoint;
              }
            }
            break;
          case 4:
            secondByte = buf[i + 1];
            thirdByte = buf[i + 2];
            fourthByte = buf[i + 3];
            if ((secondByte & 0xc0) === 0x80 && (thirdByte & 0xc0) === 0x80 && (fourthByte & 0xc0) === 0x80) {
              tempCodePoint = ((firstByte & 0xf) << 0x12) | ((secondByte & 0x3f) << 0xc) | ((thirdByte & 0x3f) << 0x6) | (fourthByte & 0x3f);
              if (tempCodePoint > 0xffff && tempCodePoint < 0x110000) {
                codePoint = tempCodePoint;
              }
            }
        }
      }
  
      if (codePoint === null) {
        codePoint = 0xfffd;
        bytesPerSequence = 1;
      } else if (codePoint > 0xffff) {
        codePoint -= 0x10000;
        res.push(((codePoint >>> 10) & 0x3ff) | 0xd800);
        codePoint = 0xdc00 | (codePoint & 0x3ff);
      }
  
      res.push(codePoint);
      i += bytesPerSequence;
    }
  
    return _decodeCodePointsArray(res);
  };
  
  const _asciiSlice = (buf, start, end) => {
    let ret = "";
    end = Math.min(buf.length, end);
  
    for (let i = start; i < end; ++i) {
      ret += String.fromCharCode(buf[i] & 0x7f);
    }
    return ret;
  };
  
  const _latin1Slice = (buf, start, end) => {
    let ret = "";
    end = Math.min(buf.length, end);
  
    for (let i = start; i < end; ++i) {
      ret += String.fromCharCode(buf[i]);
    }
    return ret;
  };
  
  const _utf16leSlice = (buf, start, end) => {
    const bytes = buf.slice(start, end);
    let res = "";
    for (let i = 0; i < bytes.length - 1; i += 2) {
      res += String.fromCharCode(bytes[i] + bytes[i + 1] * 256);
    }
    return res;
  };
  
  const _base64Slice = (buf, start, end) => {
    if (start === 0 && end === buf.length) {
      return fromByteArray(buf);
    } else {
      return fromByteArray(buf.slice(start, end));
    }
  };
  
  const _decodeCodePointsArray = codePoints => {
    const len = codePoints.length;
    if (len <= MAX_ARGUMENTS_LENGTH) {
      return String.fromCharCode(codePoints);
    }
  
    let res = "";
    let i = 0;
    while (i < len) {
      res += String.fromCharCode(codePoints.slice(i, (i += MAX_ARGUMENTS_LENGTH)));
    }
    return res;
  };
  
  function bufferToString(encoding, start, end) {
    let loweredCase = false;
  
    if (start === undefined || start < 0) {
      start = 0;
    }
  
    if (start > this.length) {
      return "";
    }
  
    if (end === undefined || end > this.length) {
      end = this.length;
    }
  
    if (end <= 0) {
      return "";
    }
  
    end >>>= 0;
    start >>>= 0;
  
    if (end <= start) {
      return "";
    }
  
    if (!encoding) encoding = "utf8";
  
    while (true) {
      switch (encoding) {
        case "hex":
          return _hexSlice(this, start, end);
  
        case "utf8":
        case "utf-8":
          return _utf8Slice(this, start, end);
  
        case "ascii":
          return _asciiSlice(this, start, end);
  
        case "latin1":
        case "binary":
          return _latin1Slice(this, start, end);
  
        case "base64":
          return _base64Slice(this, start, end);
  
        case "ucs2":
        case "ucs-2":
        case "utf16le":
        case "utf-16le":
          return _utf16leSlice(this, start, end);
  
        default:
          if (loweredCase) throw new TypeError("Unknown encoding: " + encoding);
          encoding = (encoding + "").toLowerCase();
          loweredCase = true;
      }
    }
  }
  
  let stringToUInt8Array;
  
  if (/*#__PURE__*/ typeof Buffer === "function") {
    stringToUInt8Array = function (string, encoding) {
      if (Buffer.isBuffer(string)) return string;
      return Buffer.from(string, encoding);
    };
  } else {
    const _hexToUint8Array = string => {
      const strLen = string.length;
      const bufLen = strLen / 2;
  
      const buf = new Uint8Array(bufLen);
  
      const string_substr = string.substr.bind(string);
  
      for (let i = 0; i < bufLen; ++i) {
        const parsed = parseInt(string_substr(i * 2, 2), 16);
        if (isNaN(parsed)) return i;
        buf[i] = parsed;
      }
      return buf;
    };
  
    const _utf8ToUint8Array = string => {
      let codePoint;
      const length = string.length;
      let leadSurrogate = null;
      const bytes = [];
  
      const charCodeAt = string.charCodeAt.bind(string);
      const bytesPush = bytes.push.bind(bytes);
      for (let i = 0; i < length; ++i) {
        codePoint = charCodeAt(i);
  
        if (codePoint > 0xd7ff && codePoint < 0xe000) {
          if (!leadSurrogate) {
            if (codePoint > 0xdbff) {
              bytesPush(0xef, 0xbf, 0xbd);
              continue;
            } else if (i + 1 === length) {
              bytesPush(0xef, 0xbf, 0xbd);
              continue;
            }
  
            leadSurrogate = codePoint;
  
            continue;
          }
  
          if (codePoint < 0xdc00) {
            bytesPush(0xef, 0xbf, 0xbd);
            leadSurrogate = codePoint;
            continue;
          }
  
          codePoint = (((leadSurrogate - 0xd800) << 10) | (codePoint - 0xdc00)) + 0x10000;
        } else if (leadSurrogate) {
          bytesPush(0xef, 0xbf, 0xbd);
        }
  
        leadSurrogate = null;
  
        if (codePoint < 0x80) {
          bytes.push(codePoint);
        } else if (codePoint < 0x800) {
          bytes.push((codePoint >> 0x6) | 0xc0, (codePoint & 0x3f) | 0x80);
        } else if (codePoint < 0x10000) {
          bytes.push((codePoint >> 0xc) | 0xe0, ((codePoint >> 0x6) & 0x3f) | 0x80, (codePoint & 0x3f) | 0x80);
        } else if (codePoint < 0x110000) {
          bytes.push((codePoint >> 0x12) | 0xf0, ((codePoint >> 0xc) & 0x3f) | 0x80, ((codePoint >> 0x6) & 0x3f) | 0x80, (codePoint & 0x3f) | 0x80);
        } else {
          throw new Error("Invalid code point");
        }
      }
  
      return new Uint8Array(bytes);
    };
  
    const _asciiToUint8Array = string => {
      const len = string.length;
      const byteArray = new Uint8Array(len);
      const charCodeAt = string.charCodeAt.bind(string);
      for (let i = 0; i < len; ++i) {
        byteArray[i] = charCodeAt(i) & 0xff;
      }
      return byteArray;
    };
  
    const _utf16leToUint8Array = string => {
      let c, hi, lo;
      const strLen = string.length;
      const byteArray = new Uint8Array(strLen * 2);
      const charCodeAt = string.charCodeAt.bind(string);
  
      for (let i = 0; i < strLen; ++i) {
        c = charCodeAt(i);
        hi = c >> 8;
        lo = c % 256;
        byteArray[2 * i] = lo;
        byteArray[2 * i + 1] = hi;
      }
  
      return byteArray;
    };
  
    const INVALID_BASE64_RE = /[^\\+\\/\\w]/g;
  
    const _base64clean = str => {
      str = str.split("=")[0];
      str = str.trim().replace(INVALID_BASE64_RE, "");
      if (str.length < 2) return "";
      while (str.length % 4 !== 0) {
        str = str + "=";
      }
      return str;
    };
  
    const _base64ToUint8Array = string => {
      return toByteArray(_base64clean(string));
    };
  
    stringToUInt8Array = function stringToUInt8Array(string, encoding) {
      if (!encoding) encoding = "utf8";
      else encoding = encoding.toLowerCase();
  
      switch (encoding) {
        case "hex":
          return _hexToUint8Array(string);
  
        case "utf8":
        case "utf-8":
          return _utf8ToUint8Array(string);
  
        case "ascii":
        case "latin1":
        case "binary":
          return _asciiToUint8Array(string);
  
        case "base64":
          return _base64ToUint8Array(string);
  
        case "ucs2":
        case "ucs-2":
        case "utf16le":
        case "utf-16le":
          return _utf16leToUint8Array(string);
  
        default:
          throw new TypeError("Unknown encoding: " + encoding);
      }
    };
  }
  
  let readFile, readFileSync;
  
  if (isNode) {
    if (typeof require === "function") {
      /*#__PURE__*/ ({ readFile } = (0, require)("fs/promises"));
      /*#__PURE__*/ ({ readFileSync } = (0, require)("fs"));
    } else {
      let _readFileSync;
      import("fs").then(mod => { _readFileSync = mod.readFileSync; });
      const _readFile = /*#__PURE__*/ import("fs/promises").then(mod => mod.readFile);
      readFile = async fileName => {
        return await (
          await _readFile
        )(fileName);
      };
      readFileSync = fileName => {
        if (_readFileSync) {
          return _readFileSync(fileName);
        }
        throw new Error("The synchronous version of this method does not exist, please use the asynchronous version");
      };
    }
  } else {
    readFile = async fileName => {
      return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open("GET", fileName, true);
        xhr.responseType = "arraybuffer";
        xhr.onload = () => {
          if (xhr.status == 200 || (xhr.status == 0 && xhr.response)) {
            return resolve(new Uint8Array(xhr.response));
          }
          reject();
        };
        xhr.onerror = reject;
        xhr.send(null);
      });
    };
    readFileSync = fileName => {
      const xhr = new XMLHttpRequest();
      xhr.open("GET", fileName, false /* SYNCHRONOUS XHR FTW :) */);
      let returnArrayBuffer = false;
      try {
        xhr.responseType = "arraybuffer";
        returnArrayBuffer = true;
      } catch {}
      xhr.send(null);
      return returnArrayBuffer ? new Uint8Array(xhr.response) : stringToUInt8Array(xhr.responseText);
    };
  }  
  `,
  [
    'readFile',
    'readFileSync',
    'stringToUInt8Array',
    'bufferToString',
    'fromByteArray',
    'byteLength',
    'toByteArray',
  ]
)
